﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Linq.Dynamic;
using Arch.Common.IoC;
using Arch.Common.Linq.Dynamic;
using Arch.Intercep;
using Arch.Common.Reflection;
using Arch.IoC;
using Microsoft.Practices.Unity;

namespace Arch.Data
{
    public abstract class ArchRepository<T> : IRepository<T> where T : class, new()
    {
        private IInterceptorManager _interceptor;

        [OptionalDependency]
        public IInterceptorManager Interceptor
        {
            get { return _interceptor; }
            set { 
                _interceptor = value;
                _interceptor.InfrastructureLoading(this);
                _interceptor.InfrastructureLoaded(this);
            }
        }

        #region IRepository<T> Members

        public abstract T Load(object id);
        public abstract T Create(T entity);
        public abstract T Delete(T entity);
        public abstract T Update(T entity);
        public abstract T New();
        public abstract IQueryable<T> GetQuery();

        #region GetOrCreate

        /// <summary>
        /// Find an entity that match an expression
        /// This expression can only be an expression that is a list of
        /// Equality comparisaon chained by "And" operators
        /// Example : (User u) => u.FirstName == "Frank" && u.LastName = "Shmit
        /// </summary>
        /// <param name="expression">The expression</param>
        /// <returns>The entity</returns>
        public virtual T GetOrCreate(Expression<Func<T, bool>> expression)
        {
            return GetOrCreate(expression, true);
        }

        /// <summary>
        /// Get an entity if one exist or create an empty
        /// </summary>
        /// <param name="persit">Indicate if the new entity should be persited</param>
        /// <returns>The entity</returns>
        public virtual T GetOrCreate(bool persit)
        {
            var entity = GetQuery().FirstOrDefault();
            if (entity == null)
            {
                entity = new T();
                if (persit) Create(entity);
            }

            return entity;
        }

        /// <summary>
        /// Find an entity that match an expression
        /// This expression can only be an expression that is a list of
        /// Equality comparisaon chained by "And" operators
        /// Example : (User u) => u.FirstName == "Frank" && u.LastName = "Smith"
        /// </summary>
        /// <param name="expression">The expression</param>
        /// <param name="persit">Indicate if the new entity should be persited</param>
        /// <returns>The entity</returns>
        protected virtual T GetOrCreate(Expression<Func<T, bool>> expression, bool persit)
        {
            var entity = FindOne(expression);
            if (entity == null)
            {
                entity = ConvertExpressionInObject(expression);
                if (persit) Create(entity);
            }

            return entity;
        }

        #region GetOrCreate tools
        protected virtual T ConvertExpressionInObject(Expression<Func<T, bool>> e) //where T : class, new()
        {
            Expression body = Expression.MemberInit(
                Expression.New(typeof(T)),
                GetBindings(e));
            var ctor = Expression.Lambda(body);
            var f = (Func<T>)ctor.Compile();
            return f();
        }

        protected virtual IEnumerable<MemberBinding> GetBindings(Expression<Func<T, bool>> e)
        {
            var lambda = (LambdaExpression)e;
            return Explore(lambda.Body);
        }

        protected virtual IEnumerable<MemberBinding> Explore(Expression e)
        {
            var b = (BinaryExpression)e;
            if (b.NodeType == ExpressionType.Equal)
            {
                MemberInfo mi = ((MemberExpression)b.Left).Member;
                Expression value = b.Right;

                //if (b.Left.NodeType == ExpressionType.MemberAccess)
                //    mi = ((MemberExpression)b.Left).Member;
                //else if (b.Left.NodeType == ExpressionType.Constant)
                //    value = b.Left;
                //if (b.Right.NodeType == ExpressionType.MemberAccess)
                //    mi = ((MemberExpression)b.Right).Member;
                //else if (b.Right.NodeType == ExpressionType.Constant)
                //    value = b.Right;

                yield return Expression.Bind(mi, value);
            }
            else
            {
                foreach (var m in Explore(b.Left))
                    yield return m;
                foreach (var m in Explore(b.Right))
                    yield return m;
            }
        }
        #endregion
        #endregion

        public string DefaultOrder { get; set; }

        public bool IsPostOrder { get; set; }

        public virtual IEnumerable<T> Find(Expression<Func<T, bool>> expression = null, string order = null, int? max = null, int? page = null, bool? postorder = false)
        {
            IEnumerable<T> results;
            IQueryable<T> q = BaseFind(expression);

            order = order ?? DefaultOrder;
            postorder = postorder ?? IsPostOrder;

            if (max.HasValue)
            {
                page = page ?? 1;

                if (!string.IsNullOrEmpty(order))
                {
                    results = !postorder.Value ? 
                        q.OrderBy(order).Skip((page.Value - 1) * max.Value).Take(max.Value).ToList() : 
                        q.ToList().OrderBy(order).Skip((page.Value - 1) * max.Value).Take(max.Value).ToList();
                }
                else
                    results = q.Skip((page.Value - 1) * max.Value).Take(max.Value).ToList();
            }
            else
            {
                if (!string.IsNullOrEmpty(order))
                {
                    results = !postorder.Value ? 
                        q.OrderBy(order).ToList() : 
                        q.ToList().OrderBy(order).ToList();
                }
                else
                    results = q.ToList();
            }
            return results;
        }

        public T FindOne(Expression<Func<T, bool>> expression = null)
        {
            return BaseFind(expression).FirstOrDefault();
        }

        public virtual int Count(Expression<Func<T, bool>> expression = null)
        {
            return BaseFind(expression).Count();
        }

        public virtual IQueryable<T> BaseFind(Expression<Func<T, bool>> expression)
        {
            return expression == null ? GetQuery() : GetQuery().Where(expression);
        }

        public abstract T Attach(T entity, bool modified = false);
        public abstract T Detach(T entity);

        #endregion
    }

    public abstract class ArchRepository : IRepository
    {
        protected ArchRepository()
        {
            Repositories = new Dictionary<Type, object>();
        }

        [Dependency]
        public IDependencyContainer Container { get; set; }

        public bool ResolveOnce { get; set; }

        protected IDictionary<Type, object> Repositories { get; set; }

        protected IRepository<T> GetRepository<T>() where T : class, new()
        {
            if (ResolveOnce)
            {
                Type type = typeof(T);
                if (Repositories.ContainsKey(type) == false)
                {
                    Repositories.Add(type, Container.GetInstance<IRepository<T>>());
                }
                return Repositories[type] as IRepository<T>;
            }

            return Container.GetInstance<IRepository<T>>();
        }


        public T Load<T>(object id) where T : class, new()
        {
            return GetRepository<T>().Load(id);
        }

        public T Create<T>(T entity) where T : class, new()
        {
            return GetRepository<T>().Create(entity);
        }

        public T Attach<T>(T entity, bool modified = false) where T : class, new()
        {
            return GetRepository<T>().Attach(entity, modified);
        }

        public T Detach<T>(T entity) where T : class, new()
        {
            return GetRepository<T>().Detach(entity);
        }

        public T Delete<T>(T entity) where T : class, new()
        {
            return GetRepository<T>().Delete(entity);
        }

        public T Update<T>(T entity) where T : class, new()
        {
            return GetRepository<T>().Update(entity);
        }

        public T GetOrCreate<T>(Expression<Func<T, bool>> expression = null) where T : class, new()
        {
            return GetRepository<T>().GetOrCreate(expression);
        }

        public IEnumerable<T> Find<T>(Expression<Func<T, bool>> expression = null, string order = null, int? max = null, int? page = null, bool? postorder = false) where T : class, new()
        {
            return GetRepository<T>().Find(expression , order, max, page, postorder);
        }

        public T FindOne<T>(Expression<Func<T, bool>> expression = null) where T : class, new()
        {
            return GetRepository<T>().FindOne(expression);
        }

        public int Count<T>(Expression<Func<T, bool>> expression = null) where T : class, new()
        {
            return GetRepository<T>().Count(expression);
        }

        public T New<T>() where T : class, new()
        {
            return GetRepository<T>().New();
        }

        public IQueryable<T> GetQuery<T>() where T : class, new()
        {
            return GetRepository<T>().GetQuery();
        }

        public abstract void SaveChanges();
    }
}
